package com._21cn.framework.utils.image;

import java.awt.AlphaComposite;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;

/**
 * 缩放图片的工具类
 *
 */
public class ScaleImage {
	
	/**
	 * Convenience method that returns a scaled instance of the
	 * provided {@code BufferedImage}.
	 *
	 * @param img the original image to be scaled
	 * @param targetWidth the desired width of the scaled instance,
	 *    in pixels
	 * @param targetHeight the desired height of the scaled instance,
	 *    in pixels
	 * @param higherQuality if true, this method will use a multi-step
	 *    scaling technique that provides higher quality than the usual
	 *    one-step technique (only useful in down-scaling cases, where
	 *    {@code targetWidth} or {@code targetHeight} is
	 *    smaller than the original dimensions, and generally only when
	 *    the {@code BILINEAR} hint is specified)
	 * @return a scaled version of the original {@code BufferedImage}
	 */
	public static BufferedImage scaleImageByMultiStep(BufferedImage img, int targetWidth,	int targetHeight, boolean higherQuality){
		BufferedImage ret = (BufferedImage)img;
		int w, h;
		if (higherQuality) {
			w = img.getWidth();
			h = img.getHeight();
		}
		else {
			w = targetWidth;
			h = targetHeight;
		}
		
		//if( w>=ret.getWidth() && h>=ret.getHeight() )		return ret;
		
		int type = (img.getTransparency() == Transparency.OPAQUE) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
		do {
			if (higherQuality && w > targetWidth) {
				w >>= 1;
			}
			if (w < targetWidth) {
				w = targetWidth;
			}
			
			if (higherQuality && h > targetHeight) {
				h >>=1;
			}
			if (h < targetHeight) {
				h = targetHeight;
			}
			//System.out.println( "w:" + w+ ",h:" + h+ ",targetWidth:" + targetWidth+ ",targetHeight:" + targetHeight);
			ret = scaleImage(ret, type, RenderingHints.VALUE_INTERPOLATION_BILINEAR, w, h);
			
		} while (w != targetWidth || h != targetHeight);
		
		return ret;
	}
	
	/**
	 * This approach uses a special "trilinear mipmapping" technique
	 * written by Jim Graham.  Some developers may find the visual quality
	 * provided by this technique to be similar to SCALE_AREA_AVERAGING,
	 * but at a fraction of the performance cost (although visually slightly
	 * "fuzzier" perhaps).
	 */
	public  static BufferedImage scaleImageByTrilinear(BufferedImage img, int targetWidth, int targetHeight){
//		REMIND: This only works for opaque images...
		
//		Use multi-step technique: start with original size, then
//		scale down in multiple passes with drawImage()
//		until the target size is reached
		int iw = img.getWidth();
		int ih = img.getHeight();
		
		Object hint = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
		int type = (img.getTransparency() == Transparency.OPAQUE) ? 	BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
		
//		First get down to no more than 2x in W & H
		while (iw > targetWidth*2 || ih > targetHeight*2) {
			iw = (iw > targetWidth*2) ? iw/2 : iw;
			ih = (ih > targetHeight*2) ? ih/2 : ih;
			img = scaleImage(img, type, hint, iw, ih);
		}
		
//		REMIND: Conservative approach:
//		first get W right, then worry about H
		
//		If still too wide - do a horizontal trilinear blend
//		of img and a half-width img
		if (iw > targetWidth) {
			int iw2 = iw/2;
			BufferedImage img2 = scaleImage(img, type, hint, iw2, ih);
			if (iw2 < targetWidth) {
				img = scaleImage(img, type, hint, targetWidth, ih);
				img2 = scaleImage(img2, type, hint, targetWidth, ih);
				interp(img2, img, iw-targetWidth, targetWidth-iw2);
			}
			img = img2;
			iw = targetWidth;
		}
//		iw should now be targetWidth or smaller
		
//		If still too tall - do a vertical trilinear blend
//		of img and a half-height img
		if (ih > targetHeight) {
			int ih2 = ih/2;
			BufferedImage img2 = scaleImage(img, type, hint, iw, ih2);
			if (ih2 < targetHeight) {
				img = scaleImage(img, type, hint, iw, targetHeight);
				img2 = scaleImage(img2, type, hint, iw, targetHeight);
				interp(img2, img, ih-targetHeight, targetHeight-ih2);
			}
			img = img2;
			ih = targetHeight;
		}
//		ih should now be targetHeight or smaller
		
//		If we are too small, then it was probably because one of
//		the dimensions was too small from the start.
		if (iw < targetWidth && ih < targetHeight) {
			img = scaleImage(img, type, hint, targetWidth, targetHeight);
		}
		
		return img;
	}
	
	public static BufferedImage scaleImage(BufferedImage orig, int type,	Object hint, int w, int h )	{
		BufferedImage tmp = new BufferedImage(w, h, type);
		Graphics2D g2 = tmp.createGraphics();
		g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
		g2.drawImage(orig, 0, 0, w, h, null);
		g2.dispose();
		
		// method 2 : AffineTransformOp的性能比上面的Graphics.draw稍微好一丁点，但有bug，有些图片会报错，不要用
		//float sx = ((float) w) / (float)ret.getWidth();
		//float sy = ((float) h) / (float)ret.getHeight();
		//RenderingHints hints = new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR );
		//RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY );
		//AffineTransformOp op = new AffineTransformOp( AffineTransform.getScaleInstance(sx, sy), AffineTransformOp.TYPE_BILINEAR );
		//AffineTransformOp op = new AffineTransformOp( AffineTransform.getScaleInstance(sx, sy), hints );
		//tmp = op.filter(ret,tmp);
		
		return tmp;
	}
	
	private static void interp(BufferedImage img1, BufferedImage img2,int weight1,int weight2){
		float alpha = weight1;
		alpha /= (weight1 + weight2);
		Graphics2D g2 = img1.createGraphics();
		g2.setComposite( AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
		g2.drawImage(img2, 0, 0, null);
		g2.dispose();
	}
	
	public static BufferedImage scaleImageByLanczos(BufferedImage srcBufferImage,int w, int h){
		return new LanczosScaleImage().getScaledInstance( srcBufferImage, w, h );
	}
	
	public static class LanczosScaleImage {
		private int width;
		private int height;
		private int scaleWidth;
		float support = 1.0f;
		//static float PI = (float) 3.14159265358978;
		float[] contrib;
		float[] normContrib;
		float[] tmpContrib;
		int startContrib, stopContrib;
		int nDots;
		int nHalfDots;
		
		public LanczosScaleImage( float support ) {
			if( support>0.0f && support <=5.0f )
				this.support = support;
		}
		
		public LanczosScaleImage( ) {
		}
		
		/**
		 * Start:
		 * Use Lanczos filter to replace the original algorithm for image scaling. Lanczos improves quality of the scaled image
		 * modify by :blade
		 * */
		public BufferedImage getScaledInstance(BufferedImage srcBufferImage,int w, int h) {
			width = srcBufferImage.getWidth();
			height = srcBufferImage.getHeight();
			scaleWidth = w;
			
			if (DetermineResultSize(w, h) == 1) {
				return srcBufferImage;
			}
			CalContrib();
			BufferedImage pbOut = HorizontalFiltering(srcBufferImage, w);
			BufferedImage pbFinalOut = VerticalFiltering(pbOut, h);
			return pbFinalOut;
		}
		
		/**
		 * 决定图像尺寸
		 * */
		private int DetermineResultSize(int w, int h) {
			float scaleH, scaleV;
			scaleH = (float) w / (float) width;
			scaleV = (float) h / (float) height;
			//需要判断一下scaleH，scaleV，不做放大操作
			if (scaleH >= 1.0 && scaleV >= 1.0) {
				return 1;
			}
			return 0;
			
		} // end of DetermineResultSize()
/*		
		private double Lanczos(int i, int inWidth, int outWidth, float Support) {
			float x;
			
			x = (float) i * (float) outWidth / (float) inWidth;
			
			return Math.sin(x * Math.PI) / (x * Math.PI) * Math.sin(x * Math.PI / Support)
			/ (x * Math.PI / Support);
			
		} // end of Lanczos()
*/		
		private double Lanczos(float r, float rSupport) {
			return Math.sin(r) / r * Math.sin(rSupport) / rSupport;
		}
		
		//
		//   Assumption: same horizontal anｄ vertical scaling factor
		//
		private void CalContrib() {
			nHalfDots = (int) ((float) width * support / (float) scaleWidth);
			nDots = nHalfDots * 2 + 1;
			
			//System.out.println( "CalContrib: nHalfDots:" + nHalfDots +",nDots:" + nDots );
			
			try {
				contrib = new float[nDots];
				normContrib = new float[nDots];
				tmpContrib = new float[nDots];
			}
			catch (Exception e) {
				System.out.println("init contrib,normContrib,tmpContrib" + e);
			}
			
			int center = nHalfDots;
			contrib[center] = 1.0f;
			
			float weight = 0.0f;
			int i = 0;
			float rb=(float)((float) scaleWidth / (float) width *Math.PI);
			float rbSupport=rb/support  ;
			float r=rb;
			float rSupport =rbSupport;
			for (i = 1; i <= center; i++) {
				//contrib[center + i] = Lanczos(i, width, scaleWidth, support);
				
				contrib[center + i] = (float)Lanczos(r, rSupport);
				r += rb;
				rSupport +=rbSupport;
				weight += contrib[center + i];
			}
			
			for (i = center - 1; i >= 0; i--) {
				contrib[i] = contrib[center * 2 - i];
			}
			
			weight = weight * 2 + 1.0f;
			
			for (i = 0; i <= center; i++) {
				normContrib[i] = contrib[i] / weight;
			}
			
			for (i = center + 1; i < nDots; i++) {
				normContrib[i] = normContrib[center * 2 - i];
			}
		} // end of CalContrib()
		
		//处理边缘
		private void CalTempContrib(int start, int stop) {
			float weight = 0;
			
			int i = 0;
			for (i = start; i <= stop; i++) {
				weight += contrib[i];
			}
			
			for (i = start; i <= stop; i++) {
				tmpContrib[i] = contrib[i] / weight;
			}
			
		} // end of CalTempContrib()
		
		
		//行水平滤波
		private int HorizontalFilter(BufferedImage bufImg, int startX, int stopX,
				int start, int stop, int y, float[] pContrib) {
			float a = 0.0f;
			float r = 0.0f;
			float g = 0.0f;
			float b = 0.0f;
			int rgb = 0;
			//int aa, rr, gg, bb;
			int i, j;
			
			for (i = startX, j = start; i <= stopX; i++, j++) {
				rgb = bufImg.getRGB(i, y);
				//aa = rgb >>> 24;
			    //rr = (rgb >> 16) & 0xff;
			    //gg = (rgb >>  8) & 0xff;
	            //bb = rgb & 0xff;
				
				a += (rgb >>> 24) * pContrib[j];
				r += ((rgb >> 16) & 0xff) * pContrib[j];
				g += ((rgb >>  8) & 0xff) * pContrib[j];
				b += (rgb & 0xff) * pContrib[j];
			}
		    if (a < 0) {a = 0;} else if (a > 255) {a = 255;}
		    if (r < 0) {r = 0;} else if (r > 255) {r = 255;}
		    if (g < 0) {g = 0;} else if (g > 255) {g = 255;}
		    if (b < 0) {b = 0;} else if (b > 255) {b = 255;}
		    return ( ((int)a) << 24 | ((int)r) << 16 | ((int)g) << 8 | (int)b);
			
		} // end of HorizontalFilter()
		
		//图片水平滤波
		private BufferedImage HorizontalFiltering(BufferedImage bufImage, int iOutW) {
			int dwInW = bufImage.getWidth();
			int dwInH = bufImage.getHeight();
			int value = 0;
			BufferedImage pbOut = new BufferedImage(iOutW, dwInH,	BufferedImage.TYPE_INT_RGB);
			float rb = ((float) dwInW) / ((float) iOutW);
			float r = 0;
			for (int x = 0; x < iOutW; x++) {
				
				int startX;
				int start;
				int X = (int) ( r  + 0.5);
				r += rb;
				int y = 0;
				
				startX = X - nHalfDots;
				if (startX < 0) {
					startX = 0;
					start = nHalfDots - X;
				}
				else {
					start = 0;
				}
				
				int stop;
				int stopX = X + nHalfDots;
				if (stopX > (dwInW - 1)) {
					stopX = dwInW - 1;
					stop = nHalfDots + (dwInW - 1 - X);
				}
				else {
					stop = nHalfDots <<1;
				}
				
				if (start > 0 || stop < nDots - 1) {
					CalTempContrib(start, stop);
					for (y = 0; y < dwInH; y++) {
						value = HorizontalFilter(bufImage, startX, stopX, start, stop, y, tmpContrib);
						pbOut.setRGB(x, y, value);
					}
				}
				else {
					for (y = 0; y < dwInH; y++) {
						value = HorizontalFilter(bufImage, startX, stopX, start, stop, y, normContrib);
						pbOut.setRGB(x, y, value);
					}
				}
			}
			
			return pbOut;
			
		} // end of HorizontalFiltering()
		
		private int VerticalFilter(BufferedImage pbInImage, int startY, int stopY,
				int start, int stop, int x, float[] pContrib) {
			float a = 0.0f;
			float r = 0.0f;
			float g = 0.0f;
			float b = 0.0f;
			int rgb = 0;
			int i, j;
			
			for (i = startY, j = start; i <= stopY; i++, j++) {
				rgb = pbInImage.getRGB(x, i);
				
				a += (rgb >>> 24) * pContrib[j];
				r += ((rgb >> 16) & 0xff) * pContrib[j];
				g += ((rgb >>  8) & 0xff) * pContrib[j];
				b += (rgb & 0xff) * pContrib[j];
			}
		    if (a < 0) {a = 0;} else if (a > 255) {a = 255;}
		    if (r < 0) {r = 0;} else if (r > 255) {r = 255;}
		    if (g < 0) {g = 0;} else if (g > 255) {g = 255;}
		    if (b < 0) {b = 0;} else if (b > 255) {b = 255;}
		    return ( ((int)a) << 24 | ((int)r) << 16 | ((int)g) << 8 | (int)b);
			
		} // end of VerticalFilter()
		
		private BufferedImage VerticalFiltering(BufferedImage pbImage, int iOutH) {
			int iW = pbImage.getWidth();
			int iH = pbImage.getHeight();
			int value = 0;
			BufferedImage pbOut = new BufferedImage(iW, iOutH,	BufferedImage.TYPE_INT_RGB);
			
			float rb = ((float) iH) / ((float) iOutH);
			float r = 0;
			for (int y = 0; y < iOutH; y++) {
				
				int startY;
				int start;
				int Y = (int) (r + 0.5);
				r +=rb;
				
				startY = Y - nHalfDots;
				if (startY < 0) {
					startY = 0;
					start = nHalfDots - Y;
				}
				else {
					start = 0;
				}
				
				int stop;
				int stopY = Y + nHalfDots;
				if (stopY > (int) (iH - 1)) {
					stopY = iH - 1;
					stop = nHalfDots + (iH - 1 - Y);
				}
				else {
					stop = nHalfDots <<1;
				}
				
				if (start > 0 || stop < nDots - 1) {
					CalTempContrib(start, stop);
					for (int x = 0; x < iW; x++) {
						value = VerticalFilter(pbImage, startY, stopY, start, stop, x, tmpContrib);
						pbOut.setRGB(x, y, value);
					}
				}
				else {
					for (int x = 0; x < iW; x++) {
						value = VerticalFilter(pbImage, startY, stopY, start, stop, x, normContrib);
						pbOut.setRGB(x, y, value);
					}
				}
				
			}
			
			return pbOut;
			
		} // end of VerticalFiltering()
		
	}
}
